#include <kernel/shell.h>
#include <kernel/syslog.h>
#include <string.h>
#include <stdio.h>


#define  HOSTNAME  SHELL_COLOR_RED"ebraid~#"SHELL_COLOR_NONE

/**
 * shell command list head
 */
static LIST_HEAD(shell_cmd_list);

/**
 * command arguement count
 */
static int  cmd_argc = 1;
/**
 * command buffer, store the command line
 */
static char cmd_buffer[CONFIG_SHELL_BUFFER] = {0};

/**
 * arguement array pointer, max 32 arguement
 */
static char *cmd_argv[32] = {0};
static uint32_t cmd_len = 0;
static bool  cmd_dirty_flag = true;

/**
 * shell device, used to send command to uart
 */
static struct uart_device *shell_dev = NULL;


/**
 * @brief get shell command list head
 * @param none
 * @return shell command list head
 */
list_node_t* shell_get_list_head(void)
{
    return &shell_cmd_list;
}


/**
 * @brief bind uart device to shell
 * @param dev uart device
 * @return none
 */
void shell_bind_uart_device(struct uart_device *dev)
{
    shell_dev = dev;
}


/**
 * @brief get shell device
 * @param none
 * @return shell device
 */
struct uart_device* shell_device(void)
{
    return shell_dev;
}


/**
 * @brief shell command token handler
 * @param none
 * @return none
 * @note you should call this function in your uart interrupt handler
 */
void shell_token_handler(void)
{
    char token = 0;
    if(!cmd_dirty_flag) {
        token = uart_get_char(shell_dev);

        switch(token) {
            case '\r':
            case '\n':  //for x86 platform 
                cmd_buffer[cmd_len] = 0;
                cmd_len = 0;

                cmd_dirty_flag = true;
                #if !X86_64
                uart_put_char(shell_dev, '\n');
                uart_put_char(shell_dev, token);
                #endif
                break;

            case 8: 
                if(cmd_len > 0) {
                    uart_put_char(shell_dev, 127);
                    cmd_len --;
                }
                break;
            
            case 27 : // not support up, down, left, right key
                break;

            default:
                cmd_buffer[cmd_len++] = token;
                #if !X86_64
                uart_put_char(shell_dev, token);
                #endif
                break;
        }

        if(cmd_len >= CONFIG_SHELL_BUFFER) {
            cmd_len = 0;
        }
    }
    else {
        uart_get_char(shell_dev);
    }
}


/**
 * @brief shell command register
 * @param cmd shell command
 * @return int, 0: success, -1: failed
 */
int shell_cmd_register(shell_cmd_t *cmd)
{
    if(cmd->name == NULL) {
        SYSLOG_ERROR("cmd name should not be NULL");
        return -1;
    }

    if(cmd->exec == NULL) {
        SYSLOG_ERROR("cmd exec should not be NULL");
        return -1;
    }

    list_add_node_at_tail(&shell_cmd_list, &cmd->node);
    return 0;
}


/**
 * @brief shell command unregister
 * @param cmd shell command
 * @return none
 */
void shell_cmd_unregister(shell_cmd_t *cmd)
{
    list_del_node(&cmd->node);
}


/**
 * @brief shell command token parser, it will parse the command line, and store the arguement in cmd_argv
 * @param tokenline command line
 * @return none
 */
static void shell_cmd_token_parser(char *tokenline)
{
    int status = 0;
    char *p = tokenline;
    int argv_c = 0;
    cmd_argv[argv_c] = cmd_buffer;

    while(*p) {
        switch (status) {
            case 0:
                if(*p == ' ') {
                    break;
                }
                else {
                    cmd_argv[argv_c] = p;
                    argv_c ++;
                    status = 1;
                    __attribute__((fallthrough)); // Explicitly mark fall through
                }

            case 1:
                if(*p == ' ') {
                    status = 0;
                    *p = 0;
                    break;
                }
                else {
                    break;
                }
        }

        p ++;
    }

    cmd_argc = argv_c;
}


/**
 * @brief find shell command by name
 * @param name command name
 * @return shell command
 */
static shell_cmd_t* find_cmd_by_name(const char* name)
{
    shell_cmd_t *token_cmd = NULL;
    struct list_node *head = &shell_cmd_list;

    list_for_each_entry(token_cmd, head, shell_cmd_t, node) {
        
        if(strcmp(name, token_cmd->name) == 0) {
            return token_cmd;
        }
    }

    return NULL;
}


/**
 * @brief shell command loop function
 * @param none
 * @return none
 * @note you should call this function in your main loop or thread function
 */
void shell_loop(void)
{
    int ret = -1;
    char error_info[128] = {0};

    if(cmd_dirty_flag) {
        if(strcmp(cmd_buffer, "")) {
            shell_cmd_token_parser(cmd_buffer);

            shell_cmd_t *token_cmd = find_cmd_by_name(cmd_argv[0]);
            if(token_cmd) {
                ret = token_cmd->exec(cmd_argc, cmd_argv);
                if(ret) {
                    sprintf(error_info, "Failed to execute the '%s' command\r\n", cmd_argv[0]);
                    uart_put_string(shell_dev, error_info);
                }
            }
            else {
                sprintf(error_info, "The '%s' command was not found\r\n", cmd_argv[0]);
                uart_put_string(shell_dev, error_info);
            }
        }

        uart_put_string(shell_dev, HOSTNAME);
        cmd_dirty_flag = false;
    }
}
